package com.zhao.dota.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.zhao.dota.bo.AuthenticateUser;
import com.zhao.dota.contant.TeamConst;
import com.zhao.dota.mapper.LeagueMatchMapper;
import com.zhao.dota.model.*;
import com.zhao.dota.security.SecurityUtil;
import com.zhao.dota.service.*;
import com.zhao.dota.vo.LeagueMatchUsersVo;
import com.zhao.dota.vo.LeagueMatchVo;
import com.zhao.dota.vo.UserHeroVo;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

@Service
public class LeagueMatchServiceImpl extends ServiceImpl<LeagueMatchMapper, LeagueMatch> implements LeagueMatchService {

    @Resource
    private LeagueMatchUserService leagueMatchUserService;
    @Resource
    private UserService userService;
    @Resource
    private HeroService heroService;
    @Resource
    private UserGoldService userGoldService;
    @Resource
    private UserHeroService userHeroService;

    @Override
    @Transactional
    public int addMatch(Integer leagueId, boolean captainMode, List<LeagueMatchUser> users) {
        AuthenticateUser authenticateUser = SecurityUtil.getAuthenticateUser();
        LeagueMatch leagueMatch = new LeagueMatch();
        leagueMatch.setLeagueId(leagueId);
        leagueMatch.setCreateTime(LocalDateTime.now());
        leagueMatch.setCreateUserId(authenticateUser.getId());
        leagueMatch.setCaptainMode(captainMode);
        save(leagueMatch);

        for (LeagueMatchUser user : users) {
            user.setLeagueId(leagueId);
            user.setLeagueMatchId(leagueMatch.getId());
            User u = userService.getById(user.getUserId());

            if (captainMode) {
                user.setHeroId(null);
            } else {
                Hero h = heroService.getById(user.getHeroId());
                user.setHeroName(h.getName());
                user.setHeroLocalizedName(h.getLocalizedName());
            }
            user.setUserName(u.getNickName());
            user.setAvatar(u.getAvatar());

            if (Boolean.TRUE.equals(user.getDbl())) {
                LambdaQueryWrapper<UserGold> query = Wrappers.lambdaQuery(UserGold.class)
                        .eq(UserGold::getUserId, user.getUserId())
                        .eq(UserGold::getLeagueId, leagueId);
                UserGold gold = userGoldService.getOne(query);
                if (gold.getDoubleTimes() > 0) {
                    gold.setDoubleTimes(gold.getDoubleTimes() - 1);
                    userGoldService.updateById(gold);
                } else {
                    user.setDbl(false);
                }
            } else {
                user.setDbl(false);
            }
        }
        leagueMatchUserService.saveBatch(users);
        return users.size() + 1;
    }

    @Override
    public Page<LeagueMatchVo> page(Integer leagueId, Page<LeagueMatch> page) {
        LambdaQueryWrapper<LeagueMatch> query = Wrappers.lambdaQuery(LeagueMatch.class)
                .eq(LeagueMatch::getLeagueId, leagueId)
                .orderByAsc(LeagueMatch::getId);
        Page<LeagueMatch> matchPage = page(page, query);
        List<Integer> matchIds = matchPage.getRecords().stream().map(LeagueMatch::getId).collect(Collectors.toList());

        List<LeagueMatchUser> list;
        if (matchIds.isEmpty()) {
            list = Collections.emptyList();
        } else {
            LambdaQueryWrapper<LeagueMatchUser> userQuery = Wrappers.lambdaQuery(LeagueMatchUser.class)
                    .in(LeagueMatchUser::getLeagueMatchId, matchIds);
            list = leagueMatchUserService.list(userQuery);
        }
        Map<Integer, List<UserHeroVo>> userMap = list.stream()
                .map(UserHeroVo::new).collect(Collectors.groupingBy(UserHeroVo::getLeagueMatchId));

        List<LeagueMatchVo> collect = matchPage.getRecords().stream().map(m -> {
            List<UserHeroVo> users = userMap.get(m.getId());
            return new LeagueMatchVo(m, users);
        }).collect(Collectors.toList());

        Page<LeagueMatchVo> voPage = new Page<>(matchPage.getCurrent(), matchPage.getSize(), matchPage.getTotal());
        voPage.setRecords(collect);
        return voPage;
    }

    @Override
    @Transactional
    public void setWinner(Integer leagueId, Integer id, TeamConst winner) {
        LeagueMatch match = getById(id);
        if (match == null) {
            return;
        }
        match.setWinner(winner);
        updateById(match);
        LambdaQueryWrapper<LeagueMatchUser> query = Wrappers.lambdaQuery(LeagueMatchUser.class).eq(LeagueMatchUser::getLeagueMatchId, id);
        List<LeagueMatchUser> list = leagueMatchUserService.list(query);
        Map<TeamConst, List<LeagueMatchUser>> userMap = list.stream().collect(Collectors.groupingBy(LeagueMatchUser::getTeam));
        for (Map.Entry<TeamConst, List<LeagueMatchUser>> entry : userMap.entrySet()) {
            TeamConst team = entry.getKey();
            List<LeagueMatchUser> users = entry.getValue();
            if (team == winner) {
                handleWinner(match, users);
            } else { // loser
                handleLoser(match, users);
            }
        }
    }

    private void handleWinner(LeagueMatch match, List<LeagueMatchUser> users) {
        Map<Integer, LeagueMatchUser> userMap = users.stream().collect(Collectors.toMap(LeagueMatchUser::getUserId, Function.identity()));
        LambdaQueryWrapper<UserGold> query = Wrappers.lambdaQuery(UserGold.class)
                .in(UserGold::getUserId, userMap.keySet())
                .eq(UserGold::getLeagueId, match.getLeagueId());
        List<UserGold> userGolds = userGoldService.list(query);
        for (UserGold userGold : userGolds) {
            int win = 1;
            LeagueMatchUser leagueMatchUser = userMap.get(userGold.getUserId());
            if (leagueMatchUser.getDbl()) {
                win *= 2;
            }
            userGold.setGold(userGold.getGold() + win);
        }
        userGoldService.updateBatchById(userGolds);
    }

    private void handleNormalLoser(LeagueMatch match, List<LeagueMatchUser> users) {
        List<Integer> userId = new ArrayList<>(5);
        List<Integer> heroId = new ArrayList<>(5);
        Map<Integer, LeagueMatchUser> userMap = new HashMap<>(8);
        Map<Integer, LeagueMatchUser> heroMap = new HashMap<>(8);
        for (LeagueMatchUser user : users) {
            userId.add(user.getUserId());
            heroId.add(user.getHeroId());
            userMap.put(user.getUserId(), user);
            heroMap.put(user.getHeroId(), user);
        }
        LambdaQueryWrapper<UserHero> query = Wrappers.lambdaQuery(UserHero.class)
                .eq(UserHero::getLeagueId, match.getLeagueId())
                .eq(UserHero::getLost, false)
                .in(UserHero::getUserId, userId)
                .in(UserHero::getHeroId, heroId);
        List<UserHero> list = userHeroService.list(query);
        for (UserHero userHero : list) {
            LeagueMatchUser u1 = userMap.get(userHero.getUserId());
            LeagueMatchUser u2 = heroMap.get(userHero.getHeroId());
            if (!u1.getId().equals(u2.getId())) {
                continue;
            }
            if (u1.getDbl()) {
                LambdaQueryWrapper<UserGold> goldQuery = Wrappers.lambdaQuery(UserGold.class)
                        .in(UserGold::getUserId, u1.getUserId())
                        .eq(UserGold::getLeagueId, match.getLeagueId());
                UserGold gold = userGoldService.getOne(goldQuery);
                gold.setGold(gold.getGold() - 1);
                userGoldService.updateById(gold);
            }
            userHero.setLost(true);
        }
        userHeroService.updateBatchById(list);
    }

    private void handlerCaptainModeLoser(LeagueMatch match, List<LeagueMatchUser> users) {
        Map<Integer, LeagueMatchUser> userMap = users.stream().collect(Collectors.toMap(LeagueMatchUser::getUserId, Function.identity()));
        LambdaQueryWrapper<UserGold> query = Wrappers.lambdaQuery(UserGold.class)
                .in(UserGold::getUserId, userMap.keySet())
                .eq(UserGold::getLeagueId, match.getLeagueId());
        List<UserGold> userGolds = userGoldService.list(query);
        for (UserGold userGold : userGolds) {
            int win = 1;
            LeagueMatchUser leagueMatchUser = userMap.get(userGold.getUserId());
            if (leagueMatchUser.getDbl()) {
                win *= 2;
            }
            userGold.setGold(userGold.getGold() - win);
        }
        userGoldService.updateBatchById(userGolds);
    }

    private void handleLoser(LeagueMatch match, List<LeagueMatchUser> users) {
        if (match.getCaptainMode()) {
            handlerCaptainModeLoser(match, users);
        } else {
            handleNormalLoser(match, users);
        }
    }

}
